<template>
  <div class="big-file-upload-container">
    <el-upload
      ref="file"
      class="upload-demo"
      drag
      action="https://jsonplaceholder.typicode.com/posts/"
      :multiple="multiple"
      :file-list="fileList"
      :show-file-list="false"
      :before-upload="beforeUpload"
      :on-success="handleSuccess"
    ><div slot="tip" class="el-upload__tip">
       {{ tips }}
     </div>
      <i class="el-icon-upload" />
      <div class="el-upload__text">将文件拖到此处，或<em>点击选择文件</em></div>
    </el-upload>
    <div v-if="fileList.length" class="choosed-file-list">
      <table class="imagetable">
        <tr>
          <th>文件名</th>
          <th>类型</th>
          <th>大小</th>
          <th>上传状态</th>
          <th>操作</th>
        </tr>
        <tr v-for="(file, index) in fileList" :key="index">
          <td>{{ file.name }}</td>
          <td>{{ file.type }}</td>
          <td>{{ handleTransformSize(file.size) }}</td>
          <td>
            <div
              v-if="uploading && checkProgress !== 100"
              class="upload-progress"
            >
              <span>校验文件：</span>
              <el-progress
                :width="150"
                :text-inside="true"
                :stroke-width="20"
                :percentage="checkProgress"
              />
            </div>
            <div v-if="checkProgress === 100" class="upload-progress">
              <span>上传文件：</span>
              <el-progress
                :width="150"
                :text-inside="true"
                :stroke-width="20"
                :percentage="uploadProgress"
              />
            </div>
            <el-alert
              v-if="file.success"
              title="已上传"
              type="success"
              center
              show-icon
              :closable="false"
            />
          </td>
          <td>
            <el-button
              type="text"
              size="mini"
              :disabled="fileList.length === 0"
              :loading="uploading"
              @click="handleUpload"
            >上传</el-button>
            <el-button
              type="text"
              :disabled="!file.success"
              size="mini"
            >删除</el-button>
          </td>
        </tr>
      </table>
    </div>
  </div>
</template>

<script>
import SparkMD5 from 'spark-md5'
const baseUrl = 'http://127.0.0.1:7001'
/**
 * 1. 上传文件格式校验，校验无误后存放入数组
 * 2. 上传文件 - 文件生成md5，不断从FileReader变量中读取上传文件的数据块，并存放进sparkMD5字节流数组中存储
 * 3. 请求接口，传入文件名和生成的md5值到后端，后端通过文件名判断服务器是否已有该存储，如果没有则判断是否有上传了的相应MD5片段并返回结果
 * 4. 前端接收到未（完成）传递的结果，开始分片发送请求
 * 5. 分片请求结束后，通知后端可以进行字节流的合并，后返回上传成功
 */
// 分片大小
const chunkSize = 10 * 1024 * 1024
let fileSize = 0
// 总分片数
let chunks = 0
// 已上传区块数量
let hasUploaded = 0
export default {
  name: 'BigFileUpload',
  props: {
    multiple: Boolean,
    tips: {
      type: String,
      default: ''
    },
    defaultFileList: {
      type: Array,
      default() {
        return []
      }
    }
  },
  data() {
    return {
      fileList: [],
      uploading: false,
      // 校验进度
      checkProgress: 0,
      // 文件上传进度
      uploadProgress: 0
    }
  },
  methods: {
    handleSuccess(result) {},
    onPreview(file) {},
    beforeUpload(file) {
      // 格式校验
      const { type, size } = file
      console.log(file, type, this.handleTransformSize(size))
      if (this.multiple) {
        this.fileList.push(file)
      } else {
        this.fileList = [file]
      }
      return false
    },
    async handleUpload() {
      const file = this.fileList[0]
      fileSize = file.size
      this.uploading = true
      // 开始校验
      const fileMd5Value = await this.md5File(file)
      console.log(fileMd5Value)
      const checkResult = await this.checkFileMD5(file.name, fileMd5Value)
      // 文件已存在并已完整上传
      if (checkResult.file) {
        this.checkProgress = 0
        this.uploadProgress = 0
        this.uploading = false
        file.success = true
        console.log('file', file)
        this.$message.info('文件已秒传')
        return false
      }
      console.log(checkResult)
      // 开始分片上传
      await this.uploadChunks(fileMd5Value, checkResult.chunkList)
      // 通知服务器全部上传完成
      this.notifyServer(fileMd5Value)
    },
    // 文件生成md5
    md5File(file) {
      const t = this
      return new Promise((resolve, reject) => {
        const blobSlice =
          File.prototype.slice ||
          File.prototype.mozSlice ||
          File.prototype.webkitSlice
        let currentChunk = 0
        const spark = new SparkMD5.ArrayBuffer()
        const fileReader = new FileReader()
        // 数据块数量
        chunks = Math.ceil(fileSize / chunkSize)
        fileReader.onload = function(e) {
          console.log(e)
          t.checkProgress =
            Math.floor(((currentChunk + 1) / chunks) * 10000) / 100
          // 存入ArrayBuffer字节流
          spark.append(e.target.result)
          currentChunk++
          if (currentChunk < chunks) {
            loadChunk()
          } else {
            const res = spark.end()
            resolve(res)
          }
        }
        fileReader.onerror = function() {
          t.$message.warning('Oops~~ 读取文件时错误')
          reject('Oops~~ 读取文件时错误')
        }

        function loadChunk() {
          const start = currentChunk * chunkSize
          const end =
            start + chunkSize >= file.size ? file.size : start + chunkSize
          fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
        }
        loadChunk()
      })
    },
    // 检验文件MD5值
    checkFileMD5(fileName, md5) {
      // 检查文件是否存在
      const url = `${baseUrl}/check/fileExist?fileName=${fileName}&md5=${md5}`
      return new Promise(resolve => {
        this.$rest
          .get(url)
          .then(res => {
            console.log(res)
            resolve(res)
          })
          .catch(err => {
            this.checkProgress = 0
            this.uploadProgress = 0
            this.uploading = false
            console.error(err)
          })
      })
    },
    // 分片上传
    async uploadChunks(md5, chunkList) {
      // 已经上传区块数量
      hasUploaded = chunkList.length
      for (let i = 0; i < chunks; i++) {
        const exit = ~chunkList.indexOf(i + '')
        if (!exit) {
          // const index = await this.upload(i, md5, chunks)
          await this.upload(i, md5, chunks)
          hasUploaded++
          this.uploadProgress = Math.floor((hasUploaded / chunks) * 10000) / 100
        }
      }
      return true
    },
    // 上传
    upload(i, fileMd5, chunks) {
      return new Promise(resolve => {
        const t = this
        const file = t.fileList[0]
        const end =
          (i + 1) * chunkSize >= fileSize ? fileSize : (i + 1) * chunkSize
        const formData = new FormData()
        formData.append('total', chunks)
        formData.append('index', i)
        formData.append('fileMd5', fileMd5)
        formData.append('file', file.slice(i * chunkSize, end))
        console.log(formData)
        t.$rest
          .post(`${baseUrl}/upload/file`, formData)
          .then(res => {
            console.log(res)
            resolve(res.index)
          })
          .catch(err => {
            console.error(err)
          })
      })
    },
    // 通知
    notifyServer(fileMd5) {
      const t = this
      const file = t.fileList[0]
      const url = `${baseUrl}/mergeFile?md5=${fileMd5}&fileName=${file.name}&fileSize=${fileSize}`
      t.$rest.get(url).then(res => {
        t.$message.success('上传成功')
        t.checkProgress = 0
        t.fileList = []
        t.uploadProgress = 0
        t.uploading = false
      })
    }
  }
}
</script>
